package fr.babos.core.bootstrap 
{
	/**
	 * Les imports
	 */
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.utils.getQualifiedClassName;
	import fr.babos.core.dependences.DependencesManager;
	import fr.babos.core.dependences.IDependencesManager;
	import fr.babos.core.errors.BabosError;
	import fr.babos.core.log.Log;
	import fr.babos.core.masters.IDisplayObject;
	import fr.babos.core.mvc.IController;
	import fr.babos.core.mvc.IView;
	import fr.babos.core.roles.IDisposable;
	import fr.babos.core.roles.IEngine;
	import fr.babos.core.utils.ArrayUtils;
	import fr.babos.core.utils.ClassUtils;
	import org.osflash.signals.DeluxeSignal;
	import org.osflash.signals.events.GenericEvent;
	
	/**
	 * @author ZoulouX
	 */
	public class Bootstrap implements IBootstrap
	{
		/**
		 * Lancer les vues dès que possible
		 */
		public static const ASAP						:uint						= 1;
		
		/**
		 * Lancer les vues dès que l'ancien contrôlleur donne la main au nouveau
		 */
		public static const WAIT_FOR_CONTROLLER			:uint						= 2;
		
		/**
		 * Lancer les vues une fois que toutes les anciennes vues sont parties
		 */
		public static const WAIT_FOR_ALL_VIEW			:uint						= 4;
		
		/**
		 * BETA Lancer les vues dès que la première vue est partie
		 */
		//public static const WAIT_FOR_MAIN_VIEW			:uint						= 0;
		
		/**
		 * BETA Lancer les vues dès que la vue X est partie (pour plus de vues, entrer le numéro à la main)
		 */
		//public static const WAIT_FOR_VIEW_1				:int						= 1;
		//public static const WAIT_FOR_VIEW_2				:int						= 2;
		//public static const WAIT_FOR_VIEW_3				:int						= 3;
		//public static const WAIT_FOR_VIEW_4				:int						= 4;
		
		/**
		 * La liste des commandes
		 */
		protected var _commands							:Array;
		
		/**
		 * Le controlleur en cours d'utilisation. Peut-être null.
		 */
		protected var _currentController				:IController;
		
		/**
		 * Le controlleur en attente
		 */
		protected var _waitingController				:IController;
		
		/**
		 * La commande en attente
		 */
		protected var _waitingCommand					:CommandItem;
		
		/**
		 * La dernière commande
		 */
		protected var _lastCommand						:CommandItem;
		
		/**
		 * Les paramètres en attente
		 */
		protected var _waitingParams					:Object;
		
		/**
		 * Les vues en attente d'affichage
		 */
		protected var _waitingViews						:Vector.<IView>				= Vector.<IView>([]);
		
		/**
		 * Les vues qui vont être effacées
		 */
		protected var _viewsToDelete					:Array;
		
		/**
		 * Les vues en cours d'utilisation. Peut-être vide.
		 */
		protected var _currentViews						:Array						= [];
		
		/**
		 * Le manager de dépendences de dépendences
		 */
		protected var _dependencesManager				:IDependencesManager;
		
		/**
		 * Le container pour les vues
		 */
		protected var _viewContainer					:DisplayObjectContainer;
		
		/**
		 * L'attente entre commande par défaut
		 */
		protected var _defaultWaitFor					:uint						= WAIT_FOR_ALL_VIEW;
		
		/**
		 * La commande est en train de changer (les controlleurs et vues ne sont pas encore instanciées)
		 */
		protected var _onCommandChanging				:DeluxeSignal				= new DeluxeSignal(this);
		
		/**
		 * La commande a changé (tout est instancié et animé)
		 */
		protected var _onCommandChanged					:DeluxeSignal				= new DeluxeSignal(this);
		
		/**
		 * La liste des commandes enregistrées dans le bootstrap
		 */
		public function get commands ():Array { return _commands; }
		public function set commands (value:Array):void 
		{
			_commands = value;
		}
		
		/**
		 * Le controlleur en cours d'utilisation. Peut-être null.
		 */
		public function get currentController ():IController { return _currentController; }
		
		/**
		 * Les vues en cours d'utilisation. Peut-être vide.
		 */
		public function get currentViews ():Array { return _currentViews; }
		
		/**
		 * La commande est en train de changer (les controlleurs et vues ne sont pas encore instanciées)
		 */
		public function get onCommandChanging ():DeluxeSignal { return _onCommandChanging; }
		
		/**
		 * La commande a changé (tout est instancié et animé)
		 */
		public function get onCommandChanged ():DeluxeSignal { return _onCommandChanged; }
		
		/**
		 * Le manager de dépendences de dépendences
		 */
		public function get dependencesManager ():IDependencesManager
		{
			// Si on a pas de manager de dépendences
			if (_dependencesManager == null)
			{
				// On en créé un basique par défaut
				_dependencesManager = new DependencesManager();
			}
			
			// Retourner le manageur de dépendances
			return _dependencesManager;
		}
		public function set dependencesManager (value:IDependencesManager):void 
		{
			_dependencesManager = value;
		}
		
		/**
		 * Le conteneur pour les vues
		 */
		public function get viewContainer ():DisplayObjectContainer { return _viewContainer; };
		public function set viewContainer (pValue:DisplayObjectContainer):void
		{
			_viewContainer = pValue;
		};
		
		/**
		 * L'attente entre commande par défaut
		 */
		public function get defaultWaitFor ():uint { return _defaultWaitFor; }
		public function set defaultWaitFor (value:uint):void 
		{
			_defaultWaitFor = value;
		}
		
		
		/**
		 * Le constructeur
		 * @param	pViewContainer : Le container pour les vues
		 */
		public function Bootstrap (pViewContainer:DisplayObjectContainer = null)
		{
			// Initialiser les commandes
			reset();
			
			// Le container pour les vues
			_viewContainer = pViewContainer;
		}
		
		
		/**
		 * Récupérer une commande
		 * @param	pCommandName : Le nom de la commande à récupérer
		 */
		public function getCommand (pCommandName:String):CommandItem
		{
			// Retourner la commande
			return _commands[pCommandName];
		}
		
		/**
		 * Ajouter une commande
		 * @param	pCommandName : Le nom de la commande
		 * @param	pController : La classe du controlleur qui doit être instancié
		 * @param	pAction : L'action à appeler sur le controlleur
		 * @param	pViews : Les classes des vues qui doivent être instanciées
		 * @param	pWaitFor : Quel entité attendre pour passer à la commande suivante. Voir les statiques de Bootstrap pour plus d'informations sur les options possibles.
		 * @param	pCallback : Handler appelé lorsque la commande est interceptée
		 */
		public function addCommand (pCommandName:String, pController:Class, pAction:String, pViews:Array = null, pWaitFor:int = -1, pCallback:Function = null):void 
		{
			// Vérifier la validité de la commande
			if (pCommandName != null && pCommandName.length > 0 && _commands[pCommandName] == null)
			{
				// Créer la commande
				var command:CommandItem = new CommandItem();
				
				// La configurer
				command.controller = pController;
				command.action = pAction;
				command.views = pViews;
				command.callback = pCallback;
				command.waitFor = pWaitFor == -1 ? _defaultWaitFor : pWaitFor;
				
				// Enregistrer la commande
				_commands[pCommandName] = command;
			}
			else
			{
				// Commande invalide
				throw new BabosError("Bootstrap.addCommand", "Command '"+pCommandName+"' is invalid or allready exist.");
			}
		}
		
		/**
		 * Supprimer une commande
		 * @param	pCommandName : Le nom de la commande
		 */
		public function removeCommand (pCommandName:String):void 
		{
			// Si la commande existe
			if (_commands[pCommandName] != null)
			{
				// Supprimer la commande
				_commands[pCommandName] = null;
				delete _commands[pCommandName];
			}
			else
			{
				// La commande n'existe pas
				throw new BabosError("Bootstrap.removeCommand", "Command '"+pCommandName+"' don't exist.");
			}
		}
		
		/**
		 * Ouvrir une commande
		 * @param	pCommandName : Le nom de la commande
		 * @param	pParams : Les paramètres associés à cette commande
		 */
		public function open (pCommandName:String, pParams:Object = null):void
		{
			Log.core(this, "open", arguments);
			
			// Si la commande existe
			if (_commands[pCommandName] != null)
			{
				// Si on a pas de paramètres, on créé un objet vide
				if (pParams == null)
					pParams = { };
				
				// Tout instancier sur cette commande avec ces paramètres
				instanciateFromCommand(_commands[pCommandName], pParams);
			}
			else
			{
				// La commande n'existe pas
				throw new BabosError("Bootstrap.openCommand", "Command '"+pCommandName+"' don't exist.");
			}
		}
		
		/**
		 * Tout instancier pour une commande
		 * @param	pCommand : La commande sur laquelle tout instancier
		 * @param	pParams : Les paramètres pour l'appel de l'action sur le controlleur
		 */
		protected function instanciateFromCommand (pCommand:CommandItem, pParams:Object):void
		{
			// Signaler
			_onCommandChanging.dispatch();
			
			// Supprimer controlleur et vues en attente
			deleteWaitingController();
			deleteWaitingViews();
			
			// Enregistrer la commande et les paramètres en attente
			_waitingCommand = pCommand;
			_waitingParams = pParams;
			
			// Les instances de vues pour le controlleur
			var viewsInstancesForNewController:Array = [];
			
			// Si on a un controlleur
			if (pCommand.controller != null)
			{
				// Vérifier que ça ne soit pas déjà le controlleur en cours
				if (_currentController == null || !(_currentController is pCommand.controller))
				{
					// Instancier le controlleur
					_waitingController = instanciateController(pCommand.controller);
				}
			}
			
			// Si on a une ou plusieurs vues
			if (pCommand.views != null && pCommand.views.length > 0)
			{
				// Si on doit instancier cette classe de vue
				var instanciateThisClass:Boolean;
				
				// Les vues à effacer
				_viewsToDelete = ArrayUtils.copy(_currentViews);
				
				Log.log("BOOTSTRAP", "currentViews", _currentViews);
				
				// La vue de l'itérateur
				var currentView:IView;
				
				// Parcourir les classes des vues à créer
				for each (var futureViewClass:Class in pCommand.views)
				{
					// Instancier cette classe
					instanciateThisClass = true;
					
					// Parcourir les vues actuelles
					for each (currentView in _currentViews)
					{
						// Si on a une classe de vue à créer qui existe déjà en concrête
						if (currentView is futureViewClass)
						{
							// Ne pas instancier
							instanciateThisClass = false;
							
							// Ajouter cette instance au controlleur
							viewsInstancesForNewController.push(currentView);
							
							// Ne pas effacer cette vue
							_viewsToDelete = ArrayUtils.deleteElement(_viewsToDelete, currentView);
							
							// Ne pas continuer
							break;
						}
					}
					
					// Vérifier que cette vue n'existe pas déjà
					if (instanciateThisClass)
					{
						// Instancier la vue
						currentView = instanciateView(futureViewClass);
						
						// L'ajouter au vues en attente
						_waitingViews.push(currentView);
						
						// Ajouter cette instance au controlleur
						viewsInstancesForNewController.push(currentView);
					}
				}
				
				// Injecter les nouvelles vues
				for each (var view:IView in viewsInstancesForNewController)
				{
					injectViews(view, viewsInstancesForNewController);
				}
				
				// Si on a des vues à effacer
				if (_viewsToDelete.length > 0)
				{
					Log.log("BOOTSTRAP", "_viewsToDelete", _viewsToDelete);
					
					// Parcourir les vues que l'on doit supprimer
					for each (currentView in _viewsToDelete)
					{
						// Ecouter lorsque la vue est effacée
						currentView.onTurnedOff.addOnce(viewTurnedOffHandler);
						
						// Arrêter la vue
						currentView.turnOff();
					}
				}
				else if (pCommand.waitFor == WAIT_FOR_ALL_VIEW)
				{
					// On passe directement aux nouvelles vues
					activateWaitingViews();
				}
				
				// Enregistrer les nouvelles vues
				_currentViews = viewsInstancesForNewController;
			}
			
			// Si on a un controlleur en attente
			if (_waitingController != null)
			{
				// Lui passer les instances des vues associées et autres injections
				injectViews(_waitingController, viewsInstancesForNewController);
				
				// Si on avait déjà un controlleur
				if (_currentController != null)
				{
					// Ecouter lorsque l'ancien controlleur est désactivé
					_currentController.onTurnedOff.addOnce(activateWaitingController);
					
					// Désactiver l'ancien 
					_currentController.turnOff();
				}
				else
				{
					// Activer le nouveau controlleur
					activateWaitingController();
				}
			}
			
			// Si on a pas de controlleur en attente
			else if (_currentController != null)
			{
				// Lui passer les instances des vues associées et autres injections
				injectViews(_currentController, viewsInstancesForNewController);
				
				// Si l'ancienne commande n'existe pas ou si la nouvelle commande attend le controlleur
				if (_lastCommand == null || _waitingCommand.waitFor == WAIT_FOR_CONTROLLER)
				{
					// On active les nouvelles vues
					activateWaitingViews();
				}
				
				// Appeler l'action
				callAction(_waitingCommand.action, _waitingParams);
				
				// Valider la dernière commande
				validateWaitingCommand();
			}
			
			// Si on a une ancienne commande avec un mode ASAP
			if (_lastCommand != null && _lastCommand.waitFor == ASAP)
			{
				// On active directement les nouvelles vues
				activateWaitingViews();
			}
		}
		
		/**
		 * Injecter les vues
		 * @param	pObject : L'objet sur lequel les vues vont êtres injectées
		 * @param	pViews : Les instances de vues à utiliser en tant que concrêtes temporaire d'injection
		 */
		protected function injectViews (pObject:Object, pViews:Array):void
		{
			// Si on a un injecteur de dépendences
			if (_dependencesManager != null)
			{
				// On injecte avec les vues en concrêtes temporaires
				_dependencesManager.inject(pObject, null, -1, pViews);
			}
		}
		
		/**
		 * Une vue est effacée
		 */
		protected function viewTurnedOffHandler (event:GenericEvent):void
		{
			// Si on a un event
			if (event != null)
			{
				// Récupérer la cible
				var target:DisplayObject = (event.currentTarget as IDisplayObject).getDisplayObject();
				
				// Supprimer la vue des vues à effacer
				_viewsToDelete = ArrayUtils.deleteElement(_viewsToDelete, target);
				
				// Si elle est encore contenu et si le conteneur existe
				if (_viewContainer != null && _viewContainer.contains(target))
				{
					// La supprimer du container
					_viewContainer.removeChild(target);
				}
				
				// Disposer l'élément (automatique ?)
				//(target as IDisposable).dispose();
				
				// Si la dernière commande est d'attendre toute les vues
				// Une fois que la liste est vide
				if (_lastCommand != null && _lastCommand.waitFor == WAIT_FOR_ALL_VIEW && _viewsToDelete.length == 0)
				{
					// Activer les vues
					activateWaitingViews();
				}
			}
		}
		
		/**
		 * Activer le controlleur en attente
		 */
		protected function activateWaitingController (event:GenericEvent = null):void
		{
			// Si on a un controlleur en attente
			if (_waitingController != null)
			{
				// Si on avait un ancien controlleur
				if (_currentController != null)
				{
					// Virer l'ancien controlleur
					_currentController.dispose();
				}
				
				// Séléctionner le nouveau controlleur
				_currentController = _waitingController;
				
				// On a plus de controlleur en attente
				_waitingController = null;
				
				// Activer le nouveau controlleur
				_currentController.turnOn();
				
				// Appeler l'action
				if (callAction(_waitingCommand.action, _waitingParams))
				{
					// Si l'ancienne commande n'existe pas ou si la nouvelle commande attend le controlleur
					if (_lastCommand == null || _waitingCommand.waitFor == WAIT_FOR_CONTROLLER)
					{
						// On active les nouvelles vues
						activateWaitingViews();
					}
					
					// Valider la dernière commande
					validateWaitingCommand();
				}
			}
		}
		
		/**
		 * Passer la commande en attente comme validée
		 */
		protected function validateWaitingCommand ():void
		{
			// Enregistrer cette commande comme étant la dernière
			_lastCommand = _waitingCommand;
			
			// Supprimer la commande et les paramètres en attente
			_waitingCommand = null;
			_waitingParams = null;
		}
		
		
		/**
		 * Activer les vues en attente
		 */
		protected function activateWaitingViews ():void
		{
			// Si on a des vues en attente
			if (_waitingViews.length > 0)
			{
				// Parcourir les vues
				for each (var view:IView in _waitingViews)
				{
					// Lui passer le nouveau (ou pas) controlleur
					view.controller = _currentController;
					
					// Ajouter la vue au controlleur si le controlleur existe
					if (_viewContainer != null)
						_viewContainer.addChild((view as IDisplayObject).getDisplayObject());
					
					// Activer la vue
					view.turnOn();
				}
				
				// On a plus de vues en attente
				_waitingViews = Vector.<IView>([]);
			}
			
			// Signaler
			_onCommandChanged.dispatch();
		}
		
		/**
		 * Appeler l'action sur le controlleur en cours
		 * @param	pActionName : Le nom de l'action
		 * @param	pParams : Les paramètres associés
		 */
		public function callAction (pActionName:String, pParams:Object):Boolean
		{
			// Si on a un controlleur
			if (_currentController != null)
			{
				// Appeler l'action
				return _currentController.callAction(pActionName, pParams);
			}
			else
			{
				// Controlleur introuvable
				throw new BabosError("Bootstrap.callAction", "Current controller is null, can't call action " + pActionName + ".");
			}
			
			return false;
		}
		
		/**
		 * Instancier un controlleur
		 */
		protected function instanciateController (pController:Class):IController
		{
			// Le controlleur qui va être instancié
			var controller:IController;
			
			// Essayer
			//try
			//{
				// d'instancier le controlleur
				controller = new pController () as IController;
			/*}
			catch (e:Error)
			{
				// Il y a eu un problème
				throw new BabosError("Bootstrap.instanciateController", "Error while instanciating controller " + ClassUtils.getClassName(pController) + ".");
				
				// On retourne null
				return null;
			}*/
			
			// Injecter manuellement le bootstrap
			controller.bootstrap = this;
			/*
			// Si on a un manager de dépendances
			if (_dependencesManager != null)
			{
				// Essayer d'injecter
				_dependencesManager.inject(controller);
			}*/
			
			// Retourner le controlleur
			return controller;
		}
		
		/**
		 * Instancier une vue
		 */
		protected function instanciateView (pView:Class):IView
		{
			// La vue qui va être instanciée
			var view:IView;
			
			// Essayer
			//try
			//{ 
				// d'instancier la vue
				view = new pView () as IView;
			/*}
			catch (e:Error)
			{
				// Il y a eu un problème
				throw new BabosError("Bootstrap.instanciateView", "Error while instanciating view " + ClassUtils.getClassName(pView) + ".");
				
				// On retourne null
				return null;
			}*/
			
			// Injecter manuellement le bootstrap
			view.bootstrap = this;
			
			// Si on a un manager de dépendances
			/*if (_dependencesManager != null)
			{
				// Essayer d'injecter
				_dependencesManager.inject(view);
			}*/
			
			// Retourner la vue
			return view;
		}
		
		/**
		 * Effacer le controlleur en attente
		 */
		protected function deleteWaitingController ():void
		{
			// Si on a déjà un controlleur en attente
			if (_waitingController != null)
			{
				// On le supprime
				_waitingController.dispose();
				_waitingController = null;
			}
		}
		
		/**
		 * Effacer les vues en attente
		 */
		protected function deleteWaitingViews ():void
		{
			// Si on a déjà des vues en attente
			if (_waitingViews.length > 0 && false)
			{
				Log.log("DELETE " + _waitingViews.length + " WAITING VIEWS");
				
				// Parcourir les vues en attente
				const total:uint = _waitingViews.length;
				for (var i:uint = 0; i < total; i++)
				{
					// Détruire ces vues
					_waitingViews[i].dispose();
					_waitingViews[i] = null;
					
					// L'enlever des currentViews
					_currentViews = ArrayUtils.deleteElement(_currentViews, _waitingViews[i]);
				}
				
				// Remettre le tableau à 0
				_waitingViews = Vector.<IView>([]);
			}
		}
		
		/**
		 * Virer toutes les commandes
		 */
		public function reset ():void
		{
			// Remettre les commandes à 0
			_commands = [];
			
			// Effacer le controlleur et les vues en attente
			deleteWaitingController();
			deleteWaitingViews();
		}
		
		/**
		 * Dispose
		 */
		public function dispose ():void
		{
			Log.warning("TODO : Bootstrap.dispose");
			
			reset();
		}
	}
}